﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace CodeContrib
{
	public static class SortingExtensions
	{
		public static IEnumerable<TSource> Distinct<TSource>(this IQueryable<TSource> source, Func<TSource, TSource, bool> predicate)
		{
			return source.Distinct(new PredicateEqualityComparer<TSource>(predicate));
		}

		public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
		{
			return OrderByHelper(source, propertyName, "OrderBy");
		}

		public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, Sorting sorting)
		{
			return OrderByHelper(source, sorting.PropertyName, (sorting.SortDirection == SortDirection.Ascending) ? "OrderBy" : "OrderByDescending");
		}

		public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string propertyName)
		{
			return OrderByHelper(source, propertyName, "OrderByDescending");
		}

		internal static IOrderedQueryable<T> OrderByHelper<T>(IQueryable<T> source, string propertyName, string methodName)
		{
			var type = typeof (T);
			var parameterExpression = Expression.Parameter(type, "x");
			Expression expression = parameterExpression;

			var propertyInfo = type.GetProperty(propertyName);
			expression = Expression.Property(expression, propertyInfo);
			type = propertyInfo.PropertyType;

			var delegateType = typeof (Func<,>).MakeGenericType(typeof (T), type);

			var result = typeof (Queryable).GetMethods().Single(x => x.Name == methodName && x.IsGenericMethodDefinition && x.GetGenericArguments().Length == 2 && x.GetParameters().Length == 2).MakeGenericMethod(typeof (T), type).Invoke(null, new object[]
				                                                                                                                                                                                                                                       {
					                                                                                                                                                                                                                                       source, Expression.Lambda(delegateType, expression, parameterExpression)
				                                                                                                                                                                                                                                       });

			return (IOrderedQueryable<T>)result;
		}

		public static IOrderedQueryable<T> ThenBy<T>(this IQueryable<T> source, string propertyName)
		{
			return OrderByHelper(source, propertyName, "ThenBy");
		}

		public static IOrderedQueryable<T> ThenBy<T>(this IQueryable<T> source, Sorting sorting)
		{
			return OrderByHelper(source, sorting.PropertyName, (sorting.SortDirection == SortDirection.Ascending) ? "ThenBy" : "ThenByDescending");
		}

		public static IOrderedQueryable<T> ThenByDescending<T>(this IQueryable<T> source, string propertyName)
		{
			return OrderByHelper(source, propertyName, "ThenByDescending");
		}
	}
}